/*
* 作者:蘇文宏.
* 學號:8324057
* 建議使用有 jit 的 jvm 執行,否則速度會很慢.
*/
import java.util.Random;
import java.awt.*;
import java.awt.image.*;
public class DrawPanel extends Panel implements Runnable{
MyPanel parent;//父容器是誰.
Random rand;//亂數.
int r,g,b,r1,g1,b1;//線段顏色用變數.
int x1,y1,x2,y2;//線段用之變數.
int bx,by;//透明球用之變數.
final int R_DIRECTION = 3;//紅色一次遞增量.
final int G_DIRECTION = 5;//綠色一次遞增量.
final int B_DIRECTION = 4;//藍色一次遞增量.
final int X_DIRECTION = 5;//水平方向一次遞增量.
final int Y_DIRECTION = 4;//垂直方向一次遞增量.
final int CIRCLE_COUNTER = 3;//幾次要畫出一個透明球.
int DELAY = 3;//每畫一次,延遲的時間,單位:百萬分之一秒.
double rotaryAngle = 1d;//旋轉角度.
double cosR = Math.cos(rotaryAngle);//先計算,節省時間!
double sinR = Math.sin(rotaryAngle);
double translateAngle = 0d;//移位角度.
double cosT = Math.cos(translateAngle);//先計算,節省時間!
double sinT = Math.sin(translateAngle);
final int KEEP_COLOR = 0;//保留不使用的顏色數目.
final int LINE_COLOR_BOUNDS = (256 - KEEP_COLOR) * 2;//顏色的邊界.
boolean isStop = false;//給執行緒使用的旗標.
int ballxDirection = 8;//透明球 x 方向一次遞增量.
int ballyDirection = 6;//透明球 y 方向一次遞增量.
int xsize,ysize;//此畫面之最大寬度,高度.
int radius;//透明球之球半徑.
int dstPixels[],srcPixels[];//存放透明球和背景的圖素值.
double redFactor,greenFactor,blueFactor;//透明球的漸層顏色資料.
Rectangle ball,backBall;//透明球、和背景的資料.
Point p1,p2;//線段的兩點.
Thread drawer;//執行緒.
Image backImage = null; //此畫面的備份.
Graphics backImageGc = null,mainGc = null;//備份畫面的繪圖者,及此畫面的繪圖者.
Runtime runtime = Runtime.getRuntime();
DrawPanel(MyPanel parent){
this.parent = parent;
rand = new Random();
//顏色初使化.
r = r1 = random(LINE_COLOR_BOUNDS);
g = g1 = random(LINE_COLOR_BOUNDS);
b = b1 = random(LINE_COLOR_BOUNDS);
//線段座標初使化.
x1 = random(200);
y1 = random(200);
x2 = random(200);
y2 = random(200);
p1 = new Point(x1,y1);
p2 = new Point(x2,y2);
//透明球初使化.
radius = 30;
br = br1 = random((int)(0.3 * 255));
bg = bg1 = random((int)(0.3 * 255));
bb = bb1 = random((int)(0.3 * 255));
ball = new Rectangle(bx = random(200),by = random(200),radius * 2,radius * 2);
backBall = new Rectangle(radius * 2,radius * 2);
}
//傳回正整數的亂數函式.
int random(int bounds){
if(bounds == 0)
return 0;
else
return Math.abs(rand.nextInt()) % bounds;
}
public void paint(Graphics g){
start();
}
//開始繪圖.
public void start(){
drawer=new Thread(this);
isStop=false;
drawer.start();
}
//停止繪圖.
public void stop(){
//釋放變數.
isStop = true;
drawer = null;
mainGc.dispose();
mainGc = null;
backImageGc.dispose();
backImageGc = null;
srcPixels = null;
dstPixels = null;
}
FontMetrics fm;
//執行緒之主體.
public void run(){
//取得現在時間.
long time = System.currentTimeMillis();
//取得寬度、高度.
xsize = bounds().width;
ysize = bounds().height;
//第一次、或者是改變寬度、高度時,再重新分配.
if((backImage==null) ||
(backImage.getWidth(this)!=xsize) || (backImage.getHeight(this)!=ysize)){
backImage = createImage(xsize,ysize);
backImageGc = backImage.getGraphics();
mainGc = getGraphics();
backImageGc.setFont(new Font("",Font.PLAIN,20));
fm = backImageGc.getFontMetrics();
}
//雙重繪圖區之一.
backImageGc.clearRect(0,0,xsize,ysize);
//透明球的雙重繪圖變數.
Image subImage=null,keepSubImage=null;
subImage = breateBall();
keepSubImage = createImage(new MemoryImageSource(backBall.width ,backBall.height ,
srcPixels ,0 ,backBall.width));
int times = 0;
//執行緒的工作時機.
while(Thread.currentThread() == drawer && !isStop)try{
//把彩色線段繪出.
if(parent.isDrawLine){
drawLine(backImageGc);
}
//把緩衝區繪出.
mainGc.drawImage(backImage,0,0,parent);
time += DELAY;
//每 CIRCLE_COUNTER 次繪出一次透明球.
if(parent.isDrawBall){
if(times > CIRCLE_COUNTER){
backImageGc.drawImage(keepSubImage,ball.x,ball.y,backBall.width,backBall.height,parent);
showString(backImageGc);
changeBallColor();
changeBallLocation();
subImage = breateBall();
keepSubImage = createImage(new MemoryImageSource(backBall.width ,backBall.height ,
srcPixels ,0 ,backBall.width));
backImageGc.drawImage(subImage,ball.x,ball.y,ball.width,ball.height,parent);
//因為需要大量的記憶體,所以必須時常使用 gc()
System.gc();
times = 0;
}
else
times++;
}//end of 假如要畫透明球.
Thread.sleep(Math.max(0,time - System.currentTimeMillis()));
}
catch(InterruptedException e){
}
catch(OutOfMemoryError e){
//如果記憶體不足,停止動作 3 秒,讓 gc() 清除記憶體中的垃圾.
System.gc();
try{
Thread.sleep(3000);
}
catch(InterruptedException e1){
}
}
}
int xs = -1,ys = -1;
//顯示一些資訊.
void showString(Graphics gc){
int rows = 5;
String str1 = " 透明之球 ";
String str2 = " width = " + xsize +", height = " + ysize + " ";
String str3 = " Free memory = " ;
if(xs < 0 || ys < 0){
xs = 30;
ys = (ysize - fm.getHeight() * rows) / 3;
}
//顯示字串一.
gc.setColor(Color.red);
gc.fill3DRect(xs,ys,fm.stringWidth(str1),fm.getHeight(),true);
gc.setColor(Color.blue);
gc.drawString(str1,xs,ys + fm.getAscent());
//顯示字串二.
gc.setColor(Color.blue);
gc.fill3DRect(xs,ys+fm.getHeight()*2,fm.stringWidth(str2),fm.getHeight(),false);
gc.setColor(Color.red);
gc.drawString(str2, xs,ys + fm.getAscent() + fm.getHeight()*2);
//顯示字串三.
String str = str3 + runtime.freeMemory() + " ";
gc.setColor(Color.green);
gc.fill3DRect(xs,ys+fm.getHeight()*4,fm.stringWidth(str3) * 2,fm.getHeight(),false);
gc.setColor(Color.black);
gc.drawString(str, xs,ys + fm.getAscent() + fm.getHeight()*4);
}
PixelGrabber pg;
//建立透明球.
Image breateBall(){
//備份透明球之背景.
backBall.reshape(ball.x ,ball.y ,ball.width, ball.height);
//第一次、或者是改變陣列大小時,再重新分配.
if(srcPixels == null || srcPixels.length < ball.width * ball.height){
srcPixels = new int[backBall.width * backBall.height];
dstPixels = new int[ball.width * ball.height];
}
//將影像轉成陣列.
pg = new PixelGrabber(backImage ,backBall.x ,backBall.y ,backBall.width ,backBall.height ,
srcPixels ,0 ,backBall.width);
try{
pg.grabPixels();
}
catch(InterruptedException e){
}
catch(Error e){
}
//計算透明球內部的光線曲折程度.
int x0 = radius,y0 = radius;
int index = 0;
double x,y;
int distance;
int squareRadius = radius * radius;
for(int j=0;j= 0 && yc >= 0){
///* 有深淺顏色變化之透明球.
double factor = (radius - l) / radius;
double rFactor = factor * redFactor + (1 - redFactor);
double gFactor = factor * greenFactor + (1 - greenFactor);
double bFactor = factor * blueFactor + (1 - blueFactor);
int value = srcPixels[xc + yc * ball.width];
int color = (((value >> 24) & 0xff) << 24) +
((int)Math.round(((value >> 16) & 0xff) * rFactor) << 16) +
((int)Math.round(((value >> 8) & 0xff) * gFactor) <<8) +
((int)Math.round((value & 0xff) * bFactor)) ;
dstPixels[index] = color;
// */
/* 完全透明的透明球.
dstPixels[index] = srcPixels[xc + yc * ball.width];
// */
}
else
dstPixels[index] = srcPixels[index];
}
else{
dstPixels[index] = srcPixels[index];
}
}
return createImage(new MemoryImageSource(ball.width ,ball.height ,
dstPixels ,0 ,ball.width));
}
public boolean mouseExit(Event evt,int x,int y){
if(parent.isDrawBall){
isChangeBallLocation = true;
return true;
}
return false;
}
public boolean mouseEnter(Event evt,int x,int y){
if(parent.isDrawBall){
isChangeBallLocation = false;
return true;
}
return false;
}
public boolean mouseMove(Event evt,int x,int y){
if(parent.isDrawBall){
bx = x - ball.width / 2;
by = y - ball.height;
return true;
}
return false;
}
//改變透明球位置.
boolean isChangeBallLocation = true;
void changeBallLocation(){
int W_BOUNDS = (xsize - ball.width) * 2;
int H_BOUNDS = (ysize - ball.height) * 2;
int xDirection,yDirection;
if(isChangeBallLocation){
xDirection = ballxDirection;
yDirection = ballyDirection;
}
else{
xDirection = 0;
yDirection = 0;
}
//位移.
// /*
bx += (int)Math.abs(Math.round(cosT * 2 * xDirection));
by += (int)Math.round(sinT * 2 * yDirection);
// */
if( (bx=(bx+random(xDirection)+W_BOUNDS)%W_BOUNDS) >= (W_BOUNDS/2) )
ball.x = (W_BOUNDS-1) - bx;
else
ball.x = bx;
if( (by=(by+random(yDirection)+H_BOUNDS)%H_BOUNDS) >= (H_BOUNDS/2) )
ball.y = (H_BOUNDS-1) - by;
else
ball.y = by;
}
//畫出彩色線段.
public void drawLine(Graphics gs){
changeLineColor();
gs.setColor(new Color(r,g,b));
changeLineLocation();
gs.drawLine(p1.x,p1.y,p2.x,p2.y);
}
//改變角度.
void setAngle(double dr){
if(dr <= X_DIRECTION || dr <= Y_DIRECTION)
rotaryAngle = 20 * Math.PI / 180;
else
rotaryAngle = Math.min(Math.acos(Y_DIRECTION / dr),
Math.asin(X_DIRECTION / dr));
cosR = Math.cos(rotaryAngle);
sinR = Math.sin(rotaryAngle);
translateAngle += 10 * Math.PI / 180;
translateAngle = Math.IEEEremainder(translateAngle,Math.PI * 2);
cosT = Math.cos(translateAngle);
sinT = Math.sin(translateAngle);
}
//改變線段的位置.
void changeLineLocation(){
int W_BOUNDS = xsize * 2;
int H_BOUNDS = ysize * 2;
int dx = x2 - x1;
int dy = y2 - y1;
double dr = Math.sqrt(dx*dx + dy*dy);
setAngle(dr);
int x0 = x1 + dx / 2;//(x0,y0) 中心座標.
int y0 = y1 + dy / 2;
int x,y;
//對中心點旋轉.
// /*
x = (int)(Math.round(x1 - x0) * cosR - (y1 - y0) * sinR + x0);
y = (int)(Math.round(y1 - y0) * cosR + (x1 - x0) * sinR + y0);
x1 = x; y1 = y;
x = (int)(Math.round(x2 - x0) * cosR - (y2 - y0) * sinR + x0);
y = (int)(Math.round(y2 - y0) * cosR + (x2 - x0) * sinR + y0);
x2 = x; y2 = y;
// */
if( (x1=(x1+random(X_DIRECTION)+W_BOUNDS)%W_BOUNDS) >= (W_BOUNDS/2) )
p1.x = (W_BOUNDS-1) - x1;
else
p1.x = x1;
if( (x2=(x2+random(X_DIRECTION)+W_BOUNDS)%W_BOUNDS) >= (W_BOUNDS/2) )
p2.x = (W_BOUNDS-1) - x2;
else
p2.x = x2;
if( (y1=(y1+random(Y_DIRECTION)+H_BOUNDS)%H_BOUNDS) >= (H_BOUNDS/2) )
p1.y = (H_BOUNDS-1) - y1;
else
p1.y = y1;
if( (y2=(y2+random(Y_DIRECTION)+H_BOUNDS)%H_BOUNDS) >= (H_BOUNDS/2) )
p2.y = (H_BOUNDS-1) - y2;
else
p2.y = y2;
}
//改變線段顏色.
void changeLineColor(){
if( (r1=(r1+random(R_DIRECTION))%LINE_COLOR_BOUNDS) >= (LINE_COLOR_BOUNDS/2) )
r = KEEP_COLOR + (LINE_COLOR_BOUNDS-1) - r1;
else
r = KEEP_COLOR + r1;
if( (g1=(g1+random(G_DIRECTION))%LINE_COLOR_BOUNDS) >= (LINE_COLOR_BOUNDS/2) )
g = KEEP_COLOR + (LINE_COLOR_BOUNDS-1) - g1;
else
g = KEEP_COLOR + g1;
if( (b1=(b1+random(B_DIRECTION))%LINE_COLOR_BOUNDS) >= (LINE_COLOR_BOUNDS/2) )
b = KEEP_COLOR + (LINE_COLOR_BOUNDS-1) - b1;
else
b = KEEP_COLOR + b1;
}
//改變透明球顏色.
int br,bg,bb,br1,bg1,bb1;//透明球顏色用變數.
final int CIRCLE_COLOR_BOUNDS = (int)Math.round(0.3d * 255) * 2;
void changeBallColor(){
if( (br1=(br1+ random(R_DIRECTION))%CIRCLE_COLOR_BOUNDS) >= (CIRCLE_COLOR_BOUNDS/2) )
br = (CIRCLE_COLOR_BOUNDS-1) - br1;
else
br = br1;
redFactor = br / 255d;
if( (bg1=(bg1+ random(G_DIRECTION))%CIRCLE_COLOR_BOUNDS) >= (CIRCLE_COLOR_BOUNDS/2) )
bg = (CIRCLE_COLOR_BOUNDS-1) - bg1;
else
bg = bg1;
greenFactor = bg / 255d;
if( (bb1=(bb1+ random(B_DIRECTION))%CIRCLE_COLOR_BOUNDS) >= (CIRCLE_COLOR_BOUNDS/2) )
bb = (CIRCLE_COLOR_BOUNDS-1) - bb1;
else
bb = bb1;
blueFactor = bb / 255d;
}
}//end of DrawPanel